package spar.cancer;

import spar.cancer.algorithm.invgen.InvGen;
import spar.cancer.config.CancerConfig;
import spar.cancer.data.to.dump.Dump;
import spar.cancer.data.from.ecxt.Ecxt;
import spar.cancer.algorithm.tracegrp.DoS;
import spar.cancer.algorithm.tracegrp.KMeans;
import spar.cancer.data.to.grptrace.GrpTrace;
import spar.cancer.struct.*;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

public class Cancer<T> implements CancerAPI {
    //迭代次数
    private static int iterId = 0;

    //记录该Cancer实例在第iterId次迭代时check了几次
    private int checkId;

    //最后一次check的时间
    private long checkTime;

    //类型
    private Type type;

    //名称，在objs中唯一，也即objs中不可重复存在相同name的实例
    private final String name;

    //泛型实例，支持类型见Type
    private T value;

    private static final Map<String, Cancer<Boolean>> booleanObjs = new HashMap<String, Cancer<Boolean>>();
    private static final Map<String, Cancer<Byte>> byteObjs = new HashMap<String, Cancer<Byte>>();
    private static final Map<String, Cancer<Short>> shortObjs = new HashMap<String, Cancer<Short>>();
    private static final Map<String, Cancer<Integer>> integerObjs = new HashMap<String, Cancer<Integer>>();
    private static final Map<String, Cancer<Long>> longObjs = new HashMap<String, Cancer<Long>>();
    private static final Map<String, Cancer<Character>> characterObjs = new HashMap<String, Cancer<Character>>();
    private static final Map<String, Cancer<Float>> floatObjs = new HashMap<String, Cancer<Float>>();
    private static final Map<String, Cancer<Double>> doubleObjs = new HashMap<String, Cancer<Double>>();
    private static final Map<String, Cancer<?>> unknownObjs = new HashMap<String, Cancer<?>>();
    private static final Map<String, Cancer<Double>> ecxtObjs = new HashMap<String, Cancer<Double>>();

    //静态变量，第一维为iterId，第二维为LineNumber，第三维为保存的checkInfo列表
    private static final Map<Integer, Map<Integer, List<CheckInfo>>> checkTables = new HashMap<>();

    //环境值的checkTable
    private static final Map<Integer, List<CheckInfo>> eCxtCheckTables = new HashMap<>();

    //判断该iter的checkInfos是否已经dump
    private static final Set<Integer> iterDump = new HashSet<>();
    
    //判断该lineNumber的codes是否dump
    private static final Set<Integer> lineCodesDump = new HashSet<>();

    //Map前者为iterId，后者为本次iter的Segment
    private static final Map<Integer, SegInfo> segs = new HashMap<>();

    //程序的所有行
    private static List<String> lines;

    //dump去处
    private static Dump dump;

    //ecxt数据来源
    private static Ecxt ecxt;

    //group trace输出去处
    private static GrpTrace grpTrace;

    //inv生成
    private static InvGen invGen;

    //k-means算法的k值
    private static int kmeans;

    //DoS阈值
    private static double dosThro;

    //dump flag
    private static boolean dumpFlag;

    //grouping flag
    private static boolean groupingFlag;

    //invGen flag
    private static boolean invGenFlag;

    ///////////////////////////////////////////初始化/////////////////////////////////////////////////
    public Cancer(String name, T value) {
        if (contains(name)) {
            throw new IllegalArgumentException(name + " has already exists!\n");
        }
        this.type = value == null ? Type.UNKNOWN :
                Type.valueOf(value.getClass().getSimpleName().toUpperCase());
        this.name = name;
        this.value = value;
        this.checkId = 0;
        this.checkTime = 0;
        put(this);
    }

    public Cancer(String name) {
        this(name, null);
    }

    public Cancer(T value) {
        this(String.valueOf(Thread.currentThread().getStackTrace()[2].getLineNumber()), value);
    }

    public Cancer() {
        this(String.valueOf(Thread.currentThread().getStackTrace()[2].getLineNumber()));
    }

    ////////////////////////////////////////非静态方法///////////////////////////////////////////////////
    public static int getIterId() {
        return iterId;
    }

    public int getCheckId() {
        return this.checkId;
    }

    public long getCheckTime() {
        return this.checkTime;
    }

    public Type getType() {
        return this.type;
    }

    public String getName() {
        return this.name;
    }

    public T getValue() {
        return this.value;
    }

    public void setValue(T value) {
        if (value != null) {
            this.value = value;
            if (this.type == Type.UNKNOWN) {
                this.type = Type.valueOf(value.getClass().getSimpleName().toUpperCase());
                unknownObjs.remove(this.name);
                put(this);
            }
        }
    }

    public int getCheckNum(int iterId) {
        int ret = 0;
        Map<Integer, List<CheckInfo>> checkTable = checkTables.get(iterId);
        if (checkTable != null) {
            for (List<CheckInfo> checkInfos : checkTable.values()) {
                for (CheckInfo checkInfo : checkInfos) {
                    if (checkInfo.name.equals(this.name)) {
                        ret++;
                    }
                }
            }
        }
        return ret;
    }

    public int getCheckNum() {
        int ret = 0;
        for (Map<Integer, List<CheckInfo>> checkTable : checkTables.values()) {
            for (List<CheckInfo> checkInfos : checkTable.values()) {
                for (CheckInfo checkInfo : checkInfos) {
                    if (checkInfo.name.equals(this.name)) {
                        ret++;
                    }
                }
            }
        }
        return ret;
    }

    public static Map<String, Cancer<Boolean>> getBooleanObjs() {
        return booleanObjs;
    }

    public static Map<String, Cancer<Short>> getShortObjs() {
        return shortObjs;
    }

    public static Map<String, Cancer<Integer>> getIntegerObjs() {
        return integerObjs;
    }

    public static Map<String, Cancer<Long>> getLongObjs() {
        return longObjs;
    }

    public static Map<String, Cancer<Character>> getCharacterObjs() {
        return characterObjs;
    }

    public static Map<String, Cancer<Float>> getFloatObjs() {
        return floatObjs;
    }

    public static Map<String, Cancer<Double>> getDoubleObjs() {
        return doubleObjs;
    }

    public static Map<String, Cancer<?>> getUnknownObjs() {
        return unknownObjs;
    }

    public static Map<String, Cancer<Double>> getEcxtObjs() {
        return ecxtObjs;
    }

    /////////////////////////////////////segment相关//////////////////////////////////////////////////////
    public static void setECxtObjs(Cancer<Double>... objs) {
        for (Cancer<Double> obj : objs) {
            if (obj != null) {
                ecxtObjs.put(obj.name, obj);
            }
        }
    }

    public static void setECxtObjs(List<Cancer<Double>> objs) {
        objs.forEach(Cancer::setECxtObjs);
    }

    public static void setEcxtVals(Map<String, Double> vals) {
        ecxtObjs.forEach((name, obj) -> {
            obj.setValue(vals.get(name));
        });
    }

    private static void checkECxts(int iterId) {
        List<CheckInfo> checkTable = new ArrayList<>();
        for (Cancer<Double> obj : ecxtObjs.values()) {
            checkTable.add(new CheckInfo<>(0, iterId, 0, new Date().getTime(), obj.type, obj.name, obj.value));
        }
        eCxtCheckTables.put(iterId, checkTable);
    }

    public static List<Double> getECxt(int iterId) {
        List<Double> ret = new ArrayList<>();
        List<CheckInfo> checkTable = eCxtCheckTables.get(iterId);
        if (checkTable != null) {
            checkTable.forEach(checkInfo -> {
                ret.add(((Double) checkInfo.value));
            });
        }
        return ret;
    }

    public static Map<Integer, List<Double>> getECxts() {
        Map<Integer, List<Double>> ret = new HashMap<>();
        eCxtCheckTables.forEach((iterId, checkTable) -> {
            List<Double> eCxt = new ArrayList<>();
            checkTable.forEach(checkInfo -> eCxt.add((Double) checkInfo.value));
            ret.put(iterId, eCxt);
        });
        return ret;
    }

    public static Set<Integer> getPCxt(int iterId) {
        Set<Integer> ret = new HashSet<>();
        Map<Integer, List<CheckInfo>> checkTable = checkTables.get(iterId);
        if (checkTable != null) {
            ret.addAll(checkTable.keySet());
        }
        return ret;
    }

    public static Map<Integer, Set<Integer>> getPCxts() {
        Map<Integer, Set<Integer>> ret = new HashMap<>();
        checkTables.forEach((iterId, checkTable) -> {
            Set<Integer> pCtx = new HashSet<>(checkTable.keySet());
            ret.put(iterId, pCtx);
        });
        return ret;
    }

    public static Map<Integer, List<CheckInfo>> getCheckTable(int iterId) {
        Map<Integer, List<CheckInfo>> ret = checkTables.get(iterId);
        return ret != null ? ret : new HashMap<>();
    }

    public static Map<Integer, Map<Integer, List<CheckInfo>>> getCheckTables() {
        return checkTables;
    }

    public static SegInfo getSeg(int iterId) {
        SegInfo ret = segs.get(iterId);
        return ret != null ? ret : new SegInfo(iterId);
    }

    public static Map<Integer, SegInfo> getSegs() {
        return segs;
    }

    private static void setSegECxt(int iterId) {
        segs.put(iterId, new SegInfo(iterId, getECxt(iterId)));
    }

    private static void setSegPCxtAndChktbl(int iterId) {
        SegInfo segInfo = segs.get(iterId);
        segInfo.pCxt = getPCxt(iterId);
        segInfo.checkTable = getCheckTable(iterId);
    }

    ////////////////////////////////////////obj相关//////////////////////////////////////////
    public static void put(Cancer<?> obj) {
        if (obj.type == Type.BOOLEAN) {
            booleanObjs.put(obj.name, (Cancer<Boolean>) obj);
        } else if (obj.type == Type.BYTE) {
            byteObjs.put(obj.name, (Cancer<Byte>) obj);
        } else if (obj.type == Type.SHORT) {
            shortObjs.put(obj.name, (Cancer<Short>) obj);
        } else if (obj.type == Type.INTEGER) {
            integerObjs.put(obj.name, (Cancer<Integer>) obj);
        } else if (obj.type == Type.LONG) {
            longObjs.put(obj.name, (Cancer<Long>) obj);
        } else if (obj.type == Type.CHARACTER) {
            characterObjs.put(obj.name, (Cancer<Character>) obj);
        } else if (obj.type == Type.FLOAT) {
            floatObjs.put(obj.name, (Cancer<Float>) obj);
        } else if (obj.type == Type.DOUBLE) {
            doubleObjs.put(obj.name, (Cancer<Double>) obj);
        } else if (obj.type == Type.UNKNOWN) {
            unknownObjs.put(obj.name, obj);
        }
    }
    //objs中是否包含名为name的Cancer实例
    //不包含返回CONTAINS_ERR，包含返回该实例在objs中的索引
    public static boolean contains(String name) {
        return booleanObjs.containsKey(name) ||
                byteObjs.containsKey(name) ||
                shortObjs.containsKey(name) ||
                integerObjs.containsKey(name) ||
                longObjs.containsKey(name) ||
                characterObjs.containsKey(name) ||
                floatObjs.containsKey(name) ||
                doubleObjs.containsKey(name) ||
                unknownObjs.containsKey(name);
    }

    //删除objs中名为name的Cancer实例
    public static void remove(String name) {
        if (booleanObjs.containsKey(name)) {
            booleanObjs.remove(name);
        } else if (byteObjs.containsKey(name)) {
            byteObjs.remove(name);
        } else if (shortObjs.containsKey(name)) {
            shortObjs.remove(name);
        } else if (integerObjs.containsKey(name)) {
            integerObjs.remove(name);
        } else if (longObjs.containsKey(name)) {
            longObjs.remove(name);
        } else if (characterObjs.containsKey(name)) {
            characterObjs.remove(name);
        } else if (floatObjs.containsKey(name)) {
            floatObjs.remove(name);
        } else if (doubleObjs.containsKey(name)) {
            doubleObjs.remove(name);
        } else unknownObjs.remove(name);
    }

    //返回objs中名为name的Cancer实例
    public static Cancer<?> get(String name) {
        Cancer<?> ret = null;
        if (booleanObjs.containsKey(name)) {
            ret = booleanObjs.get(name);
        } else if (byteObjs.containsKey(name)) {
            ret = byteObjs.get(name);
        } else if (shortObjs.containsKey(name)) {
            ret = shortObjs.get(name);
        } else if (integerObjs.containsKey(name)) {
            ret = integerObjs.get(name);
        } else if (longObjs.containsKey(name)) {
            ret = longObjs.get(name);
        } else if (characterObjs.containsKey(name)) {
            ret = characterObjs.get(name);
        } else if (floatObjs.containsKey(name)) {
            ret = floatObjs.get(name);
        } else if (doubleObjs.containsKey(name)) {
            ret = doubleObjs.get(name);
        } else if (unknownObjs.containsKey(name)) {
            ret = unknownObjs.get(name);
        }
        return ret;
    }

    /////////////////////////////////////流程相关////////////////////////////////////////////////
    public static void Init(CancerConfig config) throws IOException {
        //get code files content
        lines = Files.readAllLines(Paths.get(config.codeFile), StandardCharsets.UTF_8);

        //set kmeans
        kmeans = config.kmeans;

        //set dos thro
        dosThro = config.dosThro;

        //set Ecxt data source
        ecxt = config.ecxt;

        //set dump output
        dump = config.dump;

        //set group trace output
        grpTrace = config.grpTrace;

        //set inv gen algorithm
        invGen = config.invGen;

        dumpFlag = config.dumpFlag;

        groupingFlag = config.groupingFlag;

        invGenFlag = config.invGenFlag;
    }

    public static boolean hasNextEcxtData() throws IOException {
        if (ecxt.hasNextData()) {
            return true;
        } else {
            if (groupingFlag) {
                grouping();
            }
            if (invGenFlag) {
                invGen();
            }
            Exit();
            return false;
        }
    }

    public static void Exit() {
        ecxt.close();
        dump.close();
        grpTrace.close();
    }

    public static void iterEntry() {
        iterId++;
        booleanObjs.forEach((name, obj) -> {
            obj.checkId = 0;
        });
        byteObjs.forEach((name, obj) -> {
            obj.checkId = 0;
        });
        shortObjs.forEach((name, obj) -> {
            obj.checkId = 0;
        });
        integerObjs.forEach((name, obj) -> {
            obj.checkId = 0;
        });
        longObjs.forEach((name, obj) -> {
            obj.checkId = 0;
        });
        characterObjs.forEach((name, obj) -> {
            obj.checkId = 0;
        });
        floatObjs.forEach((name, obj) -> {
            obj.checkId = 0;
        });
        doubleObjs.forEach((name, obj) -> {
            obj.checkId = 0;
        });
        setEcxtVals(ecxt.getNextData());
        if (groupingFlag) {
            checkECxts(iterId);
            setSegECxt(iterId);
        }
    }

    public static void iterExit() {
        if (groupingFlag) {
            setSegPCxtAndChktbl(iterId);
        }
        if (dumpFlag) {
            dump.dumpLineCodes(dumpLineCodes(iterId));
            dump.dumpChkInfos(dumpChkInfos(iterId));
        }
    }

    public static void grouping() {
        // kmeans
        int len = ecxtObjs.size();
        KMeans km = new KMeans(new ArrayList<>(segs.values()), kmeans, 1E-10, len);
        km.run();
        // print kmeans result
//        System.out.println("kmeans:");
//        km.getGrps().forEach((grp, iters) -> {
//            System.out.println(grp + "=" + iters);
//        });

        // DoS
        DoS dos = new DoS(segs, km.getGrps(), dosThro);
        dos.run();
        // print dos result
//        System.out.println("\ndos:");
//        dos.getOutGrps().forEach((grp, iters) -> {
//            System.out.println(grp + "=" + iters);
//        });

        // Output grptrace
        grpTrace.printGrpTraces(segs, dos.getOutGrps());
    }

    public static void invGen() throws IOException {
        invGen.run();
    }

    ////////////////////////////////////check api/////////////////////////////////////////////
    //私有方法，将本次check信息保存到checkTables对应行号的表中
    private void check(int lineNumber, int iterId) {
        if (!checkTables.containsKey(iterId)) {
            checkTables.put(iterId, new HashMap<>());
        }
        Map<Integer, List<CheckInfo>> checkTable = checkTables.get(iterId);
        if (!checkTable.containsKey(lineNumber)) {
            checkTable.put(lineNumber, new ArrayList<>());
        }
        List<CheckInfo> checkInfos = checkTable.get(lineNumber);
        this.checkId++;
        this.checkTime = new Date().getTime();
        checkInfos.add(new CheckInfo<>(lineNumber, iterId, this.checkId, this.checkTime, this.type, this.name, this.value));
    }

    public void check() {
        //获取调用者的行号
        int lineNumber = Thread.currentThread().getStackTrace()[2].getLineNumber();
        check(lineNumber, iterId);
    }

    public static void check(Cancer<?>... args) {
        int lineNumber = Thread.currentThread().getStackTrace()[2].getLineNumber();
        for (Cancer<?> arg : args) {
            arg.check(lineNumber, iterId);
        }
    }

    public static void check(List<Cancer<?>> args) {
        int lineNumber = Thread.currentThread().getStackTrace()[2].getLineNumber();
        for (Cancer<?> arg : args) {
            arg.check(lineNumber, iterId);
        }
    }

    public static void check(Type type) {
        int lineNumber = Thread.currentThread().getStackTrace()[2].getLineNumber();
        if (type == Type.ALL) {
            booleanObjs.forEach((key, obj) -> {
                obj.check(lineNumber, iterId);
            });
            byteObjs.forEach((key, obj) -> {
                obj.check(lineNumber, iterId);
            });
            shortObjs.forEach((key, obj) -> {
                obj.check(lineNumber, iterId);
            });
            integerObjs.forEach((key, obj) -> {
                obj.check(lineNumber, iterId);
            });
            longObjs.forEach((key, obj) -> {
                obj.check(lineNumber, iterId);
            });
            characterObjs.forEach((key, obj) -> {
                obj.check(lineNumber, iterId);
            });
            floatObjs.forEach((key, obj) -> {
                obj.check(lineNumber, iterId);
            });
            doubleObjs.forEach((key, obj) -> {
                obj.check(lineNumber, iterId);
            });
        } else {
            if (type == Type.BOOLEAN) {
                booleanObjs.forEach((key, obj) -> {
                    obj.check(lineNumber, iterId);
                });
            } else if (type == Type.BYTE) {
                byteObjs.forEach((key, obj) -> {
                    obj.check(lineNumber, iterId);
                });
            } else if (type == Type.SHORT) {
                shortObjs.forEach((key, obj) -> {
                    obj.check(lineNumber, iterId);
                });
            } else if (type == Type.INTEGER) {
                integerObjs.forEach((key, obj) -> {
                    obj.check(lineNumber, iterId);
                });
            } else if (type == Type.LONG) {
                longObjs.forEach((key, obj) -> {
                    obj.check(lineNumber, iterId);
                });
            } else if (type == Type.CHARACTER) {
                characterObjs.forEach((key, obj) -> {
                    obj.check(lineNumber, iterId);
                });
            } else if (type == Type.FLOAT) {
                floatObjs.forEach((key, obj) -> {
                    obj.check(lineNumber, iterId);
                });
            } else if (type == Type.DOUBLE) {
                doubleObjs.forEach((key, obj) -> {
                    obj.check(lineNumber, iterId);
                });
            }
        }
    }

    public static void check(String... names) {
        int lineNumber = Thread.currentThread().getStackTrace()[2].getLineNumber();
        for (String name : names) {
            Cancer<?> obj = get(name);
            if (obj != null) {
                obj.check(lineNumber, iterId);
            }
        }
    }

    //////////////////////////////////////dump api/////////////////////////////////////////
    public List<CheckInfo> dumpChkInfos() {
        List<CheckInfo> ret = new ArrayList<>();
        checkTables.forEach((iterId, checkTable) -> {
            for (List<CheckInfo> checkInfos : checkTable.values()) {
                checkInfos.forEach(checkInfo -> {
                    if (checkInfo.name.equals(this.name)) {
                        ret.add(checkInfo);
                    }
                });
            }
        });
        return ret;
    }

    public static List<CheckInfo> dumpChkInfos(Cancer<?>... args) {
        List<CheckInfo> ret = new ArrayList<>();
        for (Cancer<?> arg : args) {
            ret.addAll(arg.dumpChkInfos());
        }
        return ret;
    }

    public static List<CheckInfo> dumpChkInfos(List<Cancer<?>> args) {
        List<CheckInfo> ret = new ArrayList<>();
        for (Cancer<?> arg : args) {
            ret.addAll(arg.dumpChkInfos());
        }
        return ret;
    }

    public static List<CheckInfo> dumpChkInfos(Type type) {
        List<CheckInfo> ret = new ArrayList<>();
        if (type == Type.ALL) {
            booleanObjs.forEach((key, obj) -> {
                ret.addAll(obj.dumpChkInfos());
            });
            byteObjs.forEach((key, obj) -> {
                ret.addAll(obj.dumpChkInfos());
            });
            shortObjs.forEach((key, obj) -> {
                ret.addAll(obj.dumpChkInfos());
            });
            integerObjs.forEach((key, obj) -> {
                ret.addAll(obj.dumpChkInfos());
            });
            longObjs.forEach((key, obj) -> {
                ret.addAll(obj.dumpChkInfos());
            });
            characterObjs.forEach((key, obj) -> {
                ret.addAll(obj.dumpChkInfos());
            });
            floatObjs.forEach((key, obj) -> {
                ret.addAll(obj.dumpChkInfos());
            });
            doubleObjs.forEach((key, obj) -> {
                ret.addAll(obj.dumpChkInfos());
            });
        } else {
            if (type == Type.BOOLEAN) {
                booleanObjs.forEach((key, obj) -> {
                    ret.addAll(obj.dumpChkInfos());
                });
            } else if (type == Type.BYTE) {
                byteObjs.forEach((key, obj) -> {
                    ret.addAll(obj.dumpChkInfos());
                });
            } else if (type == Type.SHORT) {
                shortObjs.forEach((key, obj) -> {
                    ret.addAll(obj.dumpChkInfos());
                });
            } else if (type == Type.INTEGER) {
                integerObjs.forEach((key, obj) -> {
                    ret.addAll(obj.dumpChkInfos());
                });
            } else if (type == Type.LONG) {
                longObjs.forEach((key, obj) -> {
                    ret.addAll(obj.dumpChkInfos());
                });
            } else if (type == Type.CHARACTER) {
                characterObjs.forEach((key, obj) -> {
                    ret.addAll(obj.dumpChkInfos());
                });
            } else if (type == Type.FLOAT) {
                floatObjs.forEach((key, obj) -> {
                    ret.addAll(obj.dumpChkInfos());
                });
            } else if (type == Type.DOUBLE) {
                doubleObjs.forEach((key, obj) -> {
                    ret.addAll(obj.dumpChkInfos());
                });
            }
        }
        return ret;
    }

    public static List<CheckInfo> dumpChkInfos(String... names) {
        List<CheckInfo> ret = new ArrayList<>();
        for (String name : names) {
            Cancer<?> obj = get(name);
            if (obj != null) {
                ret.addAll(obj.dumpChkInfos());
            }
        }
        return ret;
    }

    public static List<CheckInfo> dumpChkInfos(int iterId) {
        List<CheckInfo> ret = new ArrayList<>();
        if (!iterDump.contains(iterId)
                && checkTables.containsKey(iterId)) {
            for (List<CheckInfo> checkInfos : checkTables.get(iterId).values()) {
                ret.addAll(checkInfos);
            }
            iterDump.add(iterId);
        }
        return ret;
    }

    public static List<CheckInfo> dumpChkInfos(int... iterIds) {
        List<CheckInfo> ret = new ArrayList<>();
        for (int iterId : iterIds) {
            ret.addAll(dumpChkInfos(iterId));
        }
        return ret;
    }

    public static List<CheckInfo> dumpAllChkInfos() {
        List<CheckInfo> ret = new ArrayList<>();
        for (int iterId : checkTables.keySet()) {
            ret.addAll(dumpChkInfos(iterId));
        }
        return ret;
    }
    
    public static Map<Integer, List<String>> dumpLineCodes(int iterId) {
        Map<Integer, List<String>> ret = new HashMap<>();
        if (checkTables.containsKey(iterId)) {
            for (int line : checkTables.get(iterId).keySet()) {
                if (!lineCodesDump.contains(line)) {
                    lineCodesDump.add(line);
                    List<String> codes = new ArrayList<>();
                    int l = Math.max(line - 2, 1);
                    int r = Math.min(line + 2, lines.size());
                    for (int i = l; i <= r; i++) {
                        codes.add(lines.get(i - 1));
                    }
                    ret.put(line, codes);
                }
            }
        }
        return ret;
    }
    
    public static Map<Integer, List<String>> dumpLineCodes(int ...iterIds) {
        Map<Integer, List<String>> ret = new HashMap<>();
        for (int iterId : iterIds) {
            ret.putAll(dumpLineCodes(iterId));
        }
        return ret;
    }
    
    public static Map<Integer, List<String>> dumpAllLineCodes() {
        Map<Integer, List<String>> ret = new HashMap<>();
        for (int iterId : checkTables.keySet()) {
            ret.putAll(dumpLineCodes(iterId));
        }
        return ret;
    }

    @Override
    public String toString() {
        return name + "=" + value;
    }
}
